import { Extractor } from "./Extractor";
import { PageControler } from "./PageController";
import { TaskController, Task } from "./TaskController";



/**
 * 爬取对象，对 Puppeteer 的 Page 页面进行封装 task
 */
export class Spider {
    /** 被创建的所有爬虫 */
    private static spiders: Spider[] = [];
    /** page 控制器 */
    private pageController: PageControler = new PageControler();
    /** task 控制器 */
    private taskController: TaskController = new TaskController();
    /** 解析器数组 */
    private ExtractorClassList: typeof Extractor[] = [];

    constructor(public name: string) { }

    /**
     * 返回一个爬虫实例
     * @param name 爬虫的名字
     * @returns 
     */
    static getSpider(name: string) {
        return this.spiders.find(e => e.name == name);
    }

    /**
     * 添加一个任务
     * @param url 任务url
     * @param immediate 是否立即执行？默认是 false
     */
    public createTask = (url: string | URL, immediate: boolean = false) => this.taskController.createTask(url, immediate);
    /**
     * 添加一个提取器
     * @param ExtractorClass 提取器类
     */
    public use(ExtractorClass: typeof Extractor) {
        this.ExtractorClassList.push(ExtractorClass);
        // 调用钩子函数，通知被加载。
        ExtractorClass.loadHook(this.pageController.getPages())
    }


    /** 代替构造函数 */
    public static async create(name: string, urls: string[], n: number = 1) {
        const spider = new Spider(name);
        // 创建的所有的爬虫实例
        Spider.spiders.push(spider);
        // 1. 先等待初始化页面
        await spider.pageController.initPage(n);
        // 2. 初始化任务
        urls.forEach(e => spider.createTask(e));
        // 3. 开始执行
        spider.service();
        return spider;
    }


    /**
     * 从一个元素的属性上添加一个任务，将这个属性当作url的值
     * 一般使用 a 标签
     * @param element 元素选择器
     * @param attribute 元素属性 默认值 href
     */
    public fromElementAddTask(element_selector: String, attribute: string = 'href') {

    }

    /**
     * 开始处理
     */
    private async service() {
        while (true) {
            // 只有等待任务 或者 等待页面的时候允许堵塞。！！！
            const task = await this.taskController.waitNextTask() as Task;
            const pageItem = await this.pageController.waitSparePage();
            const page = pageItem.get(); // 将页面状态设置成"使用中"

            // 非堵塞
            page.goto(task.url)
                .then(() => {
                    // 所有的提取器
                    const allExtractor = this.ExtractorClassList.map(ExtractorClass => ExtractorClass.create(page, this, task));
                    // 等待提取完成
                    return Promise.all(allExtractor);
                })
                .finally(() => {
                    pageItem.back();// 将页面状态设置成空闲
                })
                .catch(() => task.repeat())
        }
    }

}

